﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using Microsoft.Xna.Framework;
using Newtonsoft.Json.Linq;
using SpriteAtlasRuntime;

namespace AnimationPackerEngine.Animation.CommandWriters
{
    internal abstract class Set : ICommandWriter
    {
        internal ScriptTargets Target { get; private set; }
        internal float Time { get; set; }

        internal Set(JProperty value)
        {
            Target = (ScriptTargets)Enum.Parse(typeof(ScriptTargets), value.Name, true);
            Time = 0;
        }

        public void Write(BinaryWriter binaryWriter, IWriterContext context)
        {
            binaryWriter.Write((int)ScriptCommands.Set);
            binaryWriter.Write((int)Target);

            WriteValues(binaryWriter, context);

            binaryWriter.Write(Time);
        }

        protected abstract void WriteValues(BinaryWriter binaryWriter, IWriterContext context);
    }

    internal class SetInt : Set
    {
        private int val;

        internal SetInt(JProperty value)
            : base(value)
        {
            val = (int)value.Value;
        }

        protected override void WriteValues(BinaryWriter binaryWriter, IWriterContext context)
        {
            binaryWriter.Write(val);
        }
    }

    internal class SetFloat : Set
    {
        private float val;

        internal SetFloat(JProperty value)
            : base(value)
        {
            val = (float)value.Value;
        }

        protected override void WriteValues(BinaryWriter binaryWriter, IWriterContext context)
        {
            binaryWriter.Write(val);
        }
    }

    internal class SetFloatPair : Set
    {
        private float v0;
        private float v1;

        internal SetFloatPair(JProperty value)
            : base(value)
        {
            v0 = (float)value.Value[0];
            v1 = (float)value.Value[1];
        }

        protected override void WriteValues(BinaryWriter binaryWriter, IWriterContext context)
        {
            binaryWriter.Write(v0);
            binaryWriter.Write(v1);
        }
    }

    internal class SetRect : Set
    {
        private string mFilename;

        internal SetRect(JProperty value)
            : base(value)
        {
            mFilename = (string)value.Value;
        }

        protected override void WriteValues(BinaryWriter binaryWriter, IWriterContext context)
        {
            Rectangle rc = context.AtlasRectForFile(mFilename, context.CurrentScript);

            binaryWriter.Write(rc.X);
            binaryWriter.Write(rc.Y);
            binaryWriter.Write(rc.Width);
            binaryWriter.Write(rc.Height);
        }
    }

    internal class SetHotspot : Set
    {
        private enum Horz
        {
            Left,
            Center,
            Right,
            Custom
        }

        private enum Vert
        {
            Top,
            Center,
            Bottom,
            Custom
        }

        private Horz mHorz;
        private Vert mVert;
        private Vector2 mHotspot;
        private string mFilename;

        internal SetHotspot(JProperty value)
            : base(value)
        {
            // defaults in the case of parsing errors
            mHotspot = new Vector2();
            mHorz = Horz.Left;
            mVert = Vert.Top;

            // what do we have? if it's an object, then this is a relative hotspot
            // if it's an array, then it's just the x,y coordinates
            if (value.Value is JObject)
            {
                JProperty target = (JProperty)((JObject)value.Value).First;
                mFilename = target.Name;

                if (target.Value[0].Type != JTokenType.String)
                {
                    mHotspot.X = (float)target.Value[0];
                    mHorz = Horz.Custom;
                }
                else
                {
                    mHorz = (Horz)Enum.Parse(typeof(Horz), (string)target.Value[0], true);
                }

                if (target.Value[1].Type != JTokenType.String)
                {
                    mHotspot.Y = (float)target.Value[1];
                    mVert = Vert.Custom;
                }
                else
                {
                    mVert = (Vert)Enum.Parse(typeof(Vert), (string)target.Value[1], true);
                }
            }
            else
            {
                mHorz = Horz.Custom;
                mVert = Vert.Custom;

                mHotspot.X = (float)value.Value[0];
                mHotspot.Y = (float)value.Value[1];
            }
        }

        protected override void WriteValues(BinaryWriter binaryWriter, IWriterContext context)
        {
            Rectangle rc = new Rectangle();

            if (!string.IsNullOrEmpty(mFilename))
            {
                rc = context.AtlasRectForFile(mFilename, context.CurrentScript);
            }

            switch (mHorz)
            {
                case Horz.Left:
                    mHotspot.X = 0;
                    break;

                case Horz.Center:
                    mHotspot.X = rc.Width / 2.0f;
                    break;

                case Horz.Right:
                    mHotspot.X = rc.Width;
                    break;
            }

            switch (mVert)
            {
                case Vert.Top:
                    mHotspot.Y = 0;
                    break;

                case Vert.Center:
                    mHotspot.Y = rc.Height / 2.0f;
                    break;

                case Vert.Bottom:
                    mHotspot.Y = rc.Height;
                    break;
            }

            binaryWriter.Write(mHotspot.X);
            binaryWriter.Write(mHotspot.Y);
        }
    }

    internal class SetFlip : Set
    {
        private bool? mFlipHorz;
        private bool? mFlipVert;

        internal SetFlip(JProperty value)
            : base(value)
        {
            JObject param = (JObject)value.Value;

            mFlipHorz = (bool?)param["horizontal"];
            mFlipVert = (bool?)param["vertical"];

            if (!mFlipHorz.HasValue && !mFlipVert.HasValue)
            {
                throw new ArgumentException("set textureflip command sent without 'horizontal' or 'vertical' subtargets.");
            }
        }

        protected override void WriteValues(BinaryWriter binaryWriter, IWriterContext context)
        {
            int key = 0;

            if (mFlipHorz.HasValue)
            {
                key += 1;
            }

            if (mFlipVert.HasValue)
            {
                key += 2;
            }

            Debug.Assert(key != 0, "We shouldn't have gotten here (that's what the constructor's exception is for.");

            binaryWriter.Write(key);

            if (mFlipHorz.HasValue)
            {
                binaryWriter.Write(mFlipHorz.Value);
            }

            if (mFlipVert.HasValue)
            {
                binaryWriter.Write(mFlipVert.Value);
            }
        }
    }

    internal class SetKeyValuePair : Set
    {
        private string key;
        private float value;

        internal SetKeyValuePair(JProperty value)
            : base(value)
        {
            JObject param = (JObject)value.Value;
            JProperty keyValue = (JProperty)param.First;

            key = keyValue.Name;
            this.value = (float)keyValue.Value;
        }

        protected override void WriteValues(BinaryWriter binaryWriter, IWriterContext context)
        {
            binaryWriter.Write(key);
            binaryWriter.Write(value);
        }
    }

    internal class SetBoundingRegions : Set
    {
        private abstract class Region
        {
            protected abstract int BoundingRegionType { get; }

            public void WriteValues(BinaryWriter binaryWriter, IWriterContext context)
            {
                binaryWriter.Write(BoundingRegionType);
                WriteBoundingRegion(binaryWriter, context);
            }

            protected abstract void WriteBoundingRegion(BinaryWriter binaryWriter, IWriterContext context);
        }

        private class RectangleRegion : Region
        {
            private Vector2 mTopLeft;
            private Vector2 mSize;

            public RectangleRegion(JArray rectangle)
            {
                mTopLeft = new Vector2((float)rectangle[0], (float)rectangle[1]);
                mSize = new Vector2((float)rectangle[2], (float)rectangle[3]);
            }

            protected override int BoundingRegionType
            {
                get { return (int)BoundingRegionTypes.Rectangle; }
            }

            protected override void WriteBoundingRegion(BinaryWriter binaryWriter, IWriterContext context)
            {
                binaryWriter.Write(mTopLeft.X);
                binaryWriter.Write(mTopLeft.Y);
                binaryWriter.Write(mSize.X);
                binaryWriter.Write(mSize.Y);
            }
        }

        private class CircleRegion : Region
        {
            private Vector2 mCenter;
            private float mRadius;

            public CircleRegion(JArray circle)
            {
                mCenter = new Vector2((float)circle[0], (float)circle[1]);
                mRadius = (float)circle[2];
            }

            protected override int BoundingRegionType
            {
                get { return (int)BoundingRegionTypes.Circle; }
            }

            protected override void WriteBoundingRegion(BinaryWriter binaryWriter, IWriterContext context)
            {
                binaryWriter.Write(mCenter.X);
                binaryWriter.Write(mCenter.Y);
                binaryWriter.Write(mRadius);
            }
        }

        private List<Region> mRegions;

        internal SetBoundingRegions(JProperty value)
            : base(value)
        {
            mRegions = new List<Region>();

            JObject regionSet = (JObject)value.Value;

            foreach (JProperty property in regionSet.Properties())
            {
                if (property.Name.ToLower() == "circles")
                {
                    JArray circles = (JArray)property.Value;
                    foreach (JArray circle in circles)
                    {
                        mRegions.Add(new CircleRegion(circle));
                    }
                }
                else if (property.Name.ToLower() == "rectangles")
                {
                    JArray rectangles = (JArray)property.Value;
                    foreach (JArray rectangle in rectangles)
                    {
                        mRegions.Add(new RectangleRegion(rectangle));
                    }
                }
                else if (property.Name.ToLower() == "circle")
                {
                    mRegions.Add(new CircleRegion((JArray)property.Value));
                }
                else if (property.Name.ToLower() == "rectangle")
                {
                    mRegions.Add(new RectangleRegion((JArray)property.Value));
                }
            }
        }

        protected override void WriteValues(BinaryWriter binaryWriter, IWriterContext context)
        {
            binaryWriter.Write(mRegions.Count);
            foreach (Region region in mRegions)
            {
                region.WriteValues(binaryWriter, context);
            }
        }
    }
}
